package vfs.filter.impl;

import vfs.filter.VFSAttributeConstraint;
import vfs.filter.VFSFilter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * The VFSFilter class represents a collection of attribute
 * constraints that can be used to filter a collection of
 * objects.
 * 
 * @author Matthew Eavenson <durandal@uga.edu>
 */
public class VFSFilterImpl implements VFSFilter{
    
    // the offset of the result set when paging
    private int offset;
    
    // the record limit when paging
    private int limit;
    
    // a String prefix to be prepended to 
    // the query generated by this filter
    private String prefix;
    
    // the List of all attribute constraints
    // enforced by this filter
    private List<VFSAttributeConstraint> constraints;

    public VFSFilterImpl(){
        offset = -1;
        limit = -1;
        prefix = "";
        constraints = new ArrayList<VFSAttributeConstraint>();
    }
    
    /**
     * Sets the prefix to be used when outputting this filter
     * as a String.  In certain cases, we may need to prepend
     * operators to keep the query syntax valid, such as AND.
     * 
     * @param prefix the prefix that will be prepended to this filter
     */
    @Override
    public void setPrefix(String prefix){
        this.prefix = prefix;
    }
    
    /**
     * Returns whether this filter contains any attribute constraints.
     * 
     * @return true if this filter has any attribute constraints, false otherwise
     */
    @Override
    public boolean hasConstraints() {
        return !constraints.isEmpty();
    }

    /**
     * Adds a collection of attribute constraints derived from a Map.
     * This method assumes that all of the attribute : value pairs
     * use the equality operator.
     * 
     * @param attrbs collection of attribute : value pairs to be added to this filter
     */
    @Override
    public void addConstraints(Map<String, String> attrbs){
        for(Map.Entry<String, String> entry: attrbs.entrySet()){
            VFSAttributeConstraint ac = new VFSAttributeConstraintImpl(entry.getKey());
            ac.setValue(entry.getValue());
            ac.setOperator(VFSFilter.VFSFilterOperator.EQ);
            addConstraint(ac);
        }
    }

    /**
     * Adds an attribute constraint with a specified attribute, value, and operator.
     * 
     * @param attribute the attribute to be constrained
     * @param value     the value to constrain the attribute by
     * @param op        the operator to be applied to this constraint
     */
    @Override
    public void addConstraint(String attribute, String value, VFSFilter.VFSFilterOperator op) {
        VFSAttributeConstraint ac = new VFSAttributeConstraintImpl(attribute);
        ac.setValue(value);
        ac.setOperator(op);
        addConstraint(ac);
    }

    /**
     * Adds an attribute constraint with a specified attribute, lower value, and upper value
     * to return objects within the specified upper and lower values.
     * 
     * @param attribute  the attribute to be constrained
     * @param lowerValue the lower value of a range
     * @param upperValue the upper value of a range
     */
    @Override
    public void addWithinConstraint(String attribute, String lowerValue, String upperValue) {
        VFSAttributeConstraint ac = new VFSAttributeConstraintImpl(attribute);
        ac.setValue(lowerValue);
        ac.setRangeValue(upperValue);
        ac.setOperator(VFSFilter.VFSFilterOperator.WITHIN);
        addConstraint(ac);
    }

    /**
     * Returns a list of the constraints in effect within this filter.
     * 
     * @return a list of attribute constraints
     */
    @Override
    public List<VFSAttributeConstraint> getConstraints() {
        return constraints;
    }

    /**
     * Adds an attribute constraint to this filter.
     * 
     * @param constraint the attribute constraint object to be added
     */
    @Override
    public void addConstraint(VFSAttributeConstraint constraint) {
        constraints.add(constraint);
    }
    
    /**
     * Gets the current offset of this filter.  Used when paging.
     * 
     * @return the value of the offset.  -1 by default
     */
    @Override
    public int getOffset() {
        return offset;
    }

    /**
     * Sets the offset of this filter.  Used when paging.
     * 
     * @param offset the new value of the offset
     */
    @Override
    public void setOffset(int offset) {
        this.offset = offset;
    }

    /**
     * Gets the current limit of this filter.  Used when paging.
     * 
     * @return the value of the limit.  -1 by default
     */
    @Override
    public int getLimit() {
        return limit;
    }

    /**
     * Sets the limit of this filter.  Used when paging.
     * 
     * @param limit the new value of the limit
     */
    @Override
    public void setLimit(int limit) {
        this.limit = limit;
    }

    @Override
    public String toString () {
        if (this == null) return "";
        String op = null;
        String filterStr = null;
        String sortStr = null;
        String paging = "";
        boolean likeOP = false;
        boolean sortOP = false;
        boolean hasNonSortConstraint = false;
//        boolean startedSort = false;
        boolean firstConstraint = true;
        StringBuilder sb = new StringBuilder(prefix);
        StringBuilder sortb = new StringBuilder();
        
        for (VFSAttributeConstraint ac: getConstraints()){
            switch (ac.getOperator()) {
            case LT:     op = " < ";                       break;
            case LE:     op = " <= ";                      break;
            case EQ:     op = " = ";                       break;
            case GE:     op = " >= ";                      break;
            case GT:     op = " > ";                       break;
            case LIKE:   op = " LIKE ";     likeOP = true; break;
            case WITHIN: op = " BETWEEN ";                 break;
            case SORTBY: op = " ORDER BY "; sortOP = true; break;
            default:
                new RuntimeException("VFSFilter : unable to determine operator").printStackTrace();
            }
            if (sortOP) {
//                if(!startedSort)
                    sortb.append( " ORDER BY " +  ac.getAttribute()  + " " + ac.getValue() );
//                else
//                    sortb.append("," + ac.getAttribute()  + " " + ac.getValue());
//                startedSort = true;
                
            } else if (likeOP) {
                sb.append( ac.getAttribute() + op + "'" + ac.getValue().replaceAll("\\*", "%") + "'" );
                hasNonSortConstraint = true;
                firstConstraint = false;
            } else {
                sb.append( (!firstConstraint? " AND " : "") + ac.getAttribute() + op + "'" + ac.getValue() + "'" );
                hasNonSortConstraint = true;
                firstConstraint = false;
            }
            if (ac.getRangeValue() != null){
                sb.append( " AND " + "'" + ac.getRangeValue() + "'" );
                hasNonSortConstraint = true;
                firstConstraint = false;
            }
        }
        
        if (limit > 0)
            paging = " OFFSET " + offset + " LIMIT " + limit;
        
        filterStr = sb.toString();
        sortStr = sortb.toString();
        
        if(!hasNonSortConstraint) return sortStr + paging;
       
        return filterStr + sortStr + paging;

    }

}
